/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package agorithms;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.*;
import java.text.DecimalFormat;
/**
 *
 * @author hsb
 */
public class TopNUserBased {
    //k most similar items to item j ( 10 <= k <=30)
    int k = 20;
    //the number of items to be recommended
    int N = 10;
    Utils util = new Utils();
    
    public float computeAverage(float itemUser[][], int user, int maxItem, float result[]) {
        float average = 0;
        int dem = 0;
        for (int i = 0; i < maxItem; i++) {
            if (itemUser[user][i] != 0) {
                dem++;
                average += itemUser[user][i];
            }
        }
//        for(int i=0; i< maxItem; i++)
//        {
//            if(itemUser[i][user] != 0) {
//                dem++;
//                average += itemUser[i][user];
//            }
//        }
        result[0] = average;
        result[1] = dem;
        return (float) average;
    }

    public float computeVariance(float itemUser[][], int user, float average, int maxItem) {
        float variance = 0;
        for (int i = 0; i < maxItem; i++) {
            if (itemUser[user][i] != 0) {
                variance += (float) (itemUser[user][i] - average) * (itemUser[user][i] - average);
            }
        }
//        for(int i=0; i< maxUser; i++)
//        {
//            if(itemUser[i][user] != 0) {
//                variance += (float) (itemUser[i][user] - average)*(itemUser[i][user] - average);
//            }
//        }
        return variance;
    }

    public void normalizeRating(float itemUser[][], int maxUser, int maxItem) {
        float average = 0;
        double temp = 0;
        float variance = 0;
        float result[] = new float[2];
        for (int i = 0; i < maxUser; i++) {
            average = computeAverage(itemUser, i, maxItem, result);
            variance = computeVariance(itemUser, i, average, maxItem);
            //System.out.print(" i " + i + " avg = "+average + "\n");
            for (int j = 0; j < maxItem; j++) {
                if (itemUser[i][j] != 0) {
                    //itemUser[i][j] = (float) Math.abs(itemUser[i][j] - average);
                    itemUser[i][j] = (float) (( Math.abs(itemUser[i][j] - average))/(Math.sqrt(variance))); ///
                    //itemUser[i][j] = (itemUser[i][j] - 1) / (5-1);
                    //System.out.print(" i " + i + " va= " + itemUser[i][j] + " avg = "+average + "\n");
                }
            }
        }
    }
    //build model (page 7 - Item-Based Top-N Recommendation Algorithms.pdf)
    //itemUser: itemUser matrix 
    //maxUser: the number of user of itemUser matrix
    //maxItem: the number of item of itemUser matrix
    public double[][] buildModel( float[][] itemUser, int k, int maxUser, int maxItem) throws FileNotFoundException
    {
        // M : item-item matrix 
        double M[][] = new double[maxUser][maxUser];
        Pearson p = new Pearson();
        double max = 0;

        for(int j=0; j< maxUser; j++)
        {
            for(int i=0; i< maxUser; i++)
            {
                if(i != j ) {
                    M[i][j] = p.computeUserBasedPearson(j, i, itemUser, maxUser, maxItem);
//                    M[i][j] = p.computeCosineUser(i, j, itemUser, maxUser, maxItem);
                    //M[i][j] = p.computeCosine(j, i, itemUser, maxUser, maxItem);
                    //System.out.print(i + " vs " + j+ " = " + M[i][j] + "\n");
                }
                else {
                    M[i][j] = 0;
                }
            }
        }
        int dem  = 0;
        for(int j=0; j< maxUser; j++)
        {
            // get the kth largest values in M matrix
            max = util.getKthLargest(M, k, j, maxUser); 
            
            for(int i=0; i < maxUser; i++)
            {
                if(M[i][j] < max  ) {
                    M[i][j] = 0;
                    dem++;
                }
            }
//            System.out.print(j+ " = " + dem + " max= " + max + "\n");
            dem = 0;
        }
        return M;
    }
    
    public double[] computeFrequency(float itemUser[][], int maxUser, int maxItem, int user)
    {
        double F[] = new double[maxItem];
        int dem = 0;
        for(int i = 0; i < maxUser; i++)
        {
            for(int j = 0; j < maxItem; j++)
            {
                if( itemUser[i][j] != 0 ) {
                    F[j] += 1;
                    dem ++;
                }
            }
            
        }
        
//        System.out.print("User: " + user + " = " + dem + " \n");
//        for(int j = 0; j < maxItem; j++)
//        {
//            System.out.print("j: " + j + " = " + F[j] + " \n");
//        }
        return F;
    }
    
    public double[] getItemPurchased(float[][] itemUser, int user, int maxItem) {
        double U[] = new double[maxItem];
        for (int i = 0; i < maxItem; i++) {
            if (itemUser[user][i] != 0) {
                U[i] = 1;
            } else {
                U[i] = 0;
            }
        }
        return U;
    }
    
    // ApplyModel - page 11 - Item-Based Top-N Recommendation Algorithms.pdf
    
    
    public double[] applyModel(double M[][], float itemUser[][], double U[], int user, int N, int maxUser, int maxItem)
    {
        // X: m × 1 vector whose non-zero entries correspond to the top-N items that were recommended.
        //double X[] = new double[maxItem];
        double F[] = new double[maxItem];
        double max  = 0;
        int dem = 0;
        
        for(int i=0; i < maxUser; i++)
        {
            if( M[i][user] == 0 ) {
                for(int j=0; j < maxItem; j++)
                {
                    itemUser[i][j] = 0;
                }
            } else dem++;
            
        }
        
//        System.out.print("Dem = " + dem + "\n");
//        dem = 0;
        
        F = computeFrequency(itemUser, maxUser, maxItem, user);
        //F = util.multiplyMatrix(F, U, maxItem);
        for(int j = 0; j < maxItem; j++) {
            if(U[j] == 1 ) {
                F[j] = 0;
            }
            //System.out.print(j + " = " + itemUser[user][j] + "\n");
        }
        
        max = util.getKthLargest(F, N, maxItem);
        
        for(int j = 0; j < maxItem; j++) {
            if(F[j] < max ) {
                F[j] = 0;
            }
        }
        
        return F;
    }
    
    
    
    // get m × 1 vector U that stores the items that have already been purchased by the active user
    // The active user’s information in vector U is encoded by 
    // setting Ui = 1 if the user has purchased the ith item and zero otherwise
    
  
    // generate N recommended items for specific user.
    public double[] topN(float[][] itemUser, int user, int maxUser, int maxItem) throws FileNotFoundException
    {
        
        double M[][] = new double[maxUser][maxUser];
        double U[] = new double[maxItem];
        double X[] = new double[maxItem];
        M = buildModel(itemUser, k, maxUser, maxItem);
        X = applyModel(M, itemUser, U, user, N, maxUser, maxItem);
        
        // The output of the algorithm is an m × 1 vector x 
        // whose non-zero entries correspond to the top-N items that were recommended
        for(int x = 0; x < maxItem; x++)
        {
            if(X[x]!=0) {
                System.out.print("Item: " + (x+1) + " Value= " + X[x] + "\n");
            }
        }  
        return X;
    }
    
    
    //
    public void generateTopN(String dataset)
    {
        int MaxNumber = 2000;
        Utils util = new Utils();
        int maxItem = 0;
        int maxUser = 0;
        int maxUserBase = 0;
        float itemUser[][] = new float[MaxNumber][MaxNumber];
        float temp[][] = new float[MaxNumber][MaxNumber];
        float itemUserTest[][] = new float[MaxNumber][MaxNumber];
        int numUserItemBase[] = new int[2];
        int numUserItemTest[] = new int[2];
        numUserItemTest[0] = 0;
        numUserItemTest[1] = 0;
        //Training set 
        File base = new File("src/data/" + dataset + ".base");
        //read training set file, return itemUser matrix with maxUser row and maxItem column
        // rating = itemUser[user][item]
        itemUser = util.readFile(itemUser, numUserItemBase, base);
        
        //Testing set
        File test = new File("src/data/" + dataset + ".test");
        //read testing set file, return itemUserTest matrix with maxUser row and maxItem column
        // rating = itemUserTest[user][item]
        itemUserTest = util.readFile(itemUserTest, numUserItemTest, test);
        
        //get number of user, and number of item in matrix
        maxUser = numUserItemTest[0];
        maxItem = numUserItemBase[1];
        maxUserBase = numUserItemBase[0];
        
        // result matrix
        double X[] = new double[maxItem];
        // M matrix: item-item
        double M[][] = new double[maxUserBase][maxUserBase];
        //double model[][] = new double[maxUserBase][maxUserBase];
        // U matrix: purchased item
        double U[] = new double[maxItem];
        
        int item = 0;
        int x = 0;
        try {
            // result file
            File myfile = new File(dataset + ".user");
            FileWriter output = new FileWriter(myfile);
            //build model 
            //M = buildModel(itemUser, k, maxUserBase, maxItem);
            normalizeRating(itemUser, maxUserBase, maxItem);
            M = buildModel(itemUser, k, maxUserBase, maxItem);
            //System.out.append(maxUser + " vs "+maxItem +"\n");
            //with every user, generate N=10 items and save them in myfile.
            for(int i = 0; i < maxUser; i++)
            {
              //get purchased item from i user.
                    
                    {
                        // return result matrix with N=10 non-zero item
                        //temp = itemUser;
                        itemUser = util.readFile(itemUser, numUserItemBase, base);
                        U = getItemPurchased(itemUser, i, maxItem);
                        X = applyModel(M, itemUser, U, i, N, maxUserBase, maxItem);
                        //write to file.
                        for(int k = 0; k < N; k++)
                        {
//                            if(X[k]!=0)
                            {
                                item = util.getKthLargestPosition(X, k+1, maxUser, maxItem);
                                String s = (i+1) + "\t" + (k+1) + "\t" + (item+1) + "\t" + "0";
                                BufferedWriter buffer=new BufferedWriter(output);
                                buffer.write(s);
                                buffer.write("\n");
                                buffer.flush();
                                x++;
                            }
                        } 
                        x = 0;
                    }
//                    break;
                }
        } catch ( Exception ioe ) {
          ioe.printStackTrace();
          // nothing else you can do? Just abort for now!
          System.err.println( "[Error] could parse the file" );
        }
    }
    
    //compute hit rate.
    public void computeHitRate(String dataset, double eval[])
    {
        int MaxNumber  = 2000;
        int maxItem = 0;
        int maxUser = 0;
        Utils util = new Utils();
        float[][] test = new float[MaxNumber][MaxNumber];
        float[][] result = new float[MaxNumber][MaxNumber];
        int numUserItem[] = new int[2];
        
        //compare between testing set and result set        
        File testArr = new File("src/data/" + dataset + ".test");
        File resultArr = new File(dataset + ".user");
        // read file and return matrix
        test = util.readFile(test, numUserItem, testArr);
        
        maxUser = numUserItem[0];
        maxItem = numUserItem[1];
//        maxUser = numUserItem[0];
//        maxItem = 1682;
        
        result = util.readFile(result, numUserItem, resultArr);
        
        double HitRate = 0;
        double ARHR = 0;
        int hits = 0;
        double temp = 0;
        
        //The number of hits is the number of items in the test set 
        //that were also present in the top-N recommended items returned for each user
        for(int i = 0; i < maxUser; i++)
        {
          for(int j = 0; j < maxItem; j++)
          {
                if(test[i][j]!=0) 
                {
                  
                  for(int x = 0; x < 10; x++) 
                  {
                      //compare item in test set and item in result set.
                      //System.out.print(i + " " + (j+1) + " = " + result[i][x] + "\n");
                      if( (j+1) == result[i][x])
                      {
                          temp += (double) 1 / (x+1);
                          hits++;
                          //System.out.print(i+1 + " " + (j+1) + " = " + result[i][x] + "\n");
//                          break;
                      }
                      
                  }
                }
          }
        }
        ARHR = (double) temp/(maxUser*10);
        maxUser = 943;
        HitRate = (double) hits/(maxUser*10);
        DecimalFormat df = new DecimalFormat(".###");
        String str = df.format(HitRate);
        eval[0] = HitRate;
        str = df.format(ARHR);
        //ARHR = Double.valueOf(str);
        eval[1] = ARHR;
        
        System.out.print("ARHR == " + ARHR + "\n");
//        System.out.print("ARHR == " + hits + "\n");
        System.out.print("HitRate == " + HitRate + "\n");
    }
}